package model.tokens;

import java.util.Map;
import java.util.TreeMap;
import java.util.List;
import java.util.HashMap;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;

import model.parser.ParserException;
import util.reflection.Reflection;
import util.reflection.ReflectionException;
import util.resources.ResourceManager;

/**
 * The TokenFactory takes a string and creates a token based on which regular
 * expression it matches.
 */

public class TokenFactory<T> {
	private Map<String, List<String>> regexToTokenClass;
	private Map<String, Integer> priority;
	private String mySyntaxFileName;

	public TokenFactory(String tokenResourceFile, String syntaxFileName) {
		ResourceManager resources = ResourceManager.getInstance();
		resources.addResourcesFromFile(tokenResourceFile);
		regexToTokenClass = new TreeMap<String, List<String>>(new MapComparator());
		priority = new HashMap<String, Integer>();

		for(String pattern : resources) {
			String[] tokens = resources.getStringArray(pattern);
			priority.put(pattern, Integer.parseInt(tokens[0]));
			List<String> tokenClassNames = Arrays.asList(Arrays.copyOfRange(tokens, 1, tokens.length));
			regexToTokenClass.put(pattern, tokenClassNames);
		}

		mySyntaxFileName = syntaxFileName;
	}

	/**
	 * Gets the token map.
	 * 
	 * @return the unmodifiable token map
	 */
	public Map<String, List<String>> getRegexTokenMap() {
		return Collections.unmodifiableMap(regexToTokenClass);
	}

	/**
	 * Create a token from a string.
	 * 
	 * @param symbol a string representing a token.
	 * @return a token.
	 */
	//FIXME: This is sure to end up on the board. There's gotta be a better way.
	@SuppressWarnings("unchecked")
	public Token<T> makeToken(String symbol) {
		for(String regex : regexToTokenClass.keySet()) {
			if(symbol.matches(regex)) {
				for(String className : regexToTokenClass.get(regex)) {
					try {
						return (Token<T>) Reflection.createInstance(className, symbol, mySyntaxFileName);
					} catch(ReflectionException e) { // FIXME: Maybe extend ReflectionException so we don't have to assume.
						try {
							return (Token<T>) Reflection.createInstance(className, symbol);
						} catch(ReflectionException e2) {
							/* Right now, if we weren't able to instantiate the class, assume that
							 * the symbol didn't really match the token (for instance the symbol wasn't a function name)
							 * and continue trying to match.
							 */
							continue;
						}
					}
				}
			}
		}

		throw ParserException.BAD_TOKEN;
	}

	/**
	 * Used to order keys in the resource bundle by their priority (specified
	 * in the bundle), for iteration.
	 */
	private class MapComparator implements Comparator<String> {

		@Override
		public int compare(String arg0, String arg1) {
			return priority.get(arg0).compareTo(priority.get(arg1));
		}
	}
}
